﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CommonTypes.Client;
using Client.Model;
using System.Threading;
using CommonTypes.Server;

namespace Client.Threads
{
    public enum TransactionClientState { WAITING_VOTE_COMMIT, WAITING_DO_COMMIT, COMMITED, CANCELED}

    public class ReservationClientThread
    {
        private ClientData _clientData;

        private String _initiatorId;

        private long _ticketNumber;

        private long _slotNumber;

        private long _transactionTimeout;

        private Reservation _reservation;

        private TransactionClientState _transactionState;

        private List<Request> _requests;

        private Object _lockingObject;

        private Thread _thread;

        public Reservation Reservation
        {
            get 
            {
                lock (_lockingObject)
                {
                    return _reservation;
                }
            }
        }

        public TransactionClientState ThreadState
        {
            get
            {
                lock (_lockingObject)
                { return _transactionState; }
            }
            set
            {
                lock (_lockingObject)
                { _transactionState =  value; }
            }
        }

        public Thread ThreadObject
        {
            set { _thread = value; }
        }

        public long SlotNumber
        {
            get { return _slotNumber; }
        }

        public ReservationClientThread(ClientData clientData, long slotNumber, Reservation reservation)
        {
            _clientData = clientData;
            _reservation = reservation;
            _initiatorId = reservation.InitiatorId;
            _ticketNumber = reservation.TicketNumber;
            _slotNumber = slotNumber;
            _transactionTimeout = -1;
            _transactionState = TransactionClientState.WAITING_VOTE_COMMIT;
            _requests = new List<Request>();
            _lockingObject = new Object();
        }

        public long TransactionTimeout
        {
            set { _transactionTimeout = value; }
        }

        public void AddInitiatorRequest(Request request)
        {
            lock (_lockingObject)
            {
                _requests.Add(request);
                Monitor.Pulse(_lockingObject);
            }
        }

        public void Run()
        {
            while (_transactionState != TransactionClientState.CANCELED && _transactionState != TransactionClientState.COMMITED)
            {
                lock (_lockingObject)
                {
                    Monitor.Wait(_lockingObject, (int)_transactionTimeout);

                    while (_requests.Count > 0)
                    {
                        Request request = _requests[0];

                        if (request.RequestProtocolPhase == RequestPhase.ABORT && request.SlotNumber == _slotNumber)
                        {
                            _transactionState = TransactionClientState.CANCELED;
                            break;
                        }
                        else if (request.RequestProtocolPhase == RequestPhase.REQUEST_COMMIT &&
                            _transactionState == TransactionClientState.WAITING_VOTE_COMMIT && request.SlotNumber == _slotNumber)
                        {
                            bool voteCommitAnswer = _clientData.ProcessVoteCommit(_slotNumber, _ticketNumber);
                           
                            IClientToClient initiatorProxy = GetInitiatorProxy(_initiatorId);
                            initiatorProxy.CommitRequestAnswer(_clientData.ClientId, _ticketNumber, _slotNumber, voteCommitAnswer);

                            if (voteCommitAnswer)
                            {
                                _transactionState = TransactionClientState.WAITING_DO_COMMIT;
                                break;
                            }
                            else
                            {
                                _transactionState = TransactionClientState.CANCELED;
                                break;
                            }
                        }
                        else if (request.RequestProtocolPhase == RequestPhase.COMMIT &&
                            _transactionState == TransactionClientState.WAITING_DO_COMMIT && request.SlotNumber == _slotNumber)
                        {
                            bool doCommitAnswer = _clientData.ProcessDoCommit(_slotNumber, _ticketNumber);

                            IClientToClient initiatorProxy = GetInitiatorProxy(_initiatorId);
                            initiatorProxy.DoCommitAnswer(_clientData.ClientId, _ticketNumber, _slotNumber, doCommitAnswer);

                            if (doCommitAnswer)
                            {
                                _transactionState = TransactionClientState.COMMITED;
                                _reservation.ReservationState = ReservationState.SUCCEEDED;
                                Console.WriteLine("--> Commited Reservation {0} initiated by {1} on slot {2}.", _ticketNumber, _initiatorId, _slotNumber);
                                return;
                            }
                            else
                            {
                                _transactionState = TransactionClientState.CANCELED;
                                break;
                            }
                        }

                        _requests.RemoveAt(0);
                    }
                }
            }
            if (_transactionState == TransactionClientState.CANCELED)
            {
                _clientData.RemoveTbReservation(_slotNumber, _ticketNumber);
                _clientData.InitiatedClientReservations.Remove(_ticketNumber);
                _reservation.ReservationState = ReservationState.CANCELED;
            }
        }

        /*public void ClearSlots()
        {
            _clientData.RemoveTbReservation(_slotNumber, _ticketNumber);
        }*/

        private IClientToClient GetInitiatorProxy(String clientId)
        {
            Random random = new Random();
            IClientToServer clientToServer = _clientData.GetRandomServer();

            String clientAddress = clientToServer.ResolveClient(clientId);

            return (IClientToClient)Activator.GetObject(typeof(IClientToClient), clientAddress + "/ClientToClient");
        }
    }
}
